﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Cngot.Extensions.Logger
{
    public class BaseLogger : ILogger, IDisposable
    {
        protected readonly string Category;
        protected readonly IServiceProvider ServiceProvider;
        protected readonly string AppId;
        public BaseLogger(IServiceProvider serviceProvider, string category)
        {
            this.ServiceProvider = serviceProvider;
            this.Category = category ?? "";
            this.AppId = serviceProvider.GetRequiredService<IConfiguration>()["Apollo:AppId"];
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return this;
        }
        /// <summary>
        /// 释放
        /// </summary>
        public virtual void Dispose()
        {
            GC.Collect();
        }

        /// <summary>
        /// 是否记录日志
        /// </summary>
        /// <param name="logLevel"></param>
        /// <returns></returns>
        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TState"></typeparam>
        /// <param name="logLevel"></param>
        /// <param name="eventId"></param>
        /// <param name="state"></param>
        /// <param name="exception"></param>
        /// <param name="formatter"></param>
        public virtual void WriteLog<TState>(Func<TState, Exception, string> formatter, LoggerMessage formatMessage)
        {

        }
        /// <summary>
        /// 日志记录
        /// </summary>
        /// <typeparam name="TState"></typeparam>
        /// <param name="logLevel"></param>
        /// <param name="eventId"></param>
        /// <param name="state"></param>
        /// <param name="exception"></param>
        /// <param name="formatter"></param>
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, string> formatter)
        {
            if (IsEnabled(logLevel))
            {
                try
                {
                    var message = new LoggerMessage
                    {
                        Trace = GetTrace(),
                        EventId = eventId,
                        Category = Category,
                        Error = ErrorMessage.Parse(exception),
                        LogLevel = logLevel.ToString(),
                        State = state,
                        Message = formatter?.Invoke(state, exception),
                        CreateTime = DateTime.Now
                    };

                    if (state is IEnumerable<KeyValuePair<string, object>> keyValuePairs)
                    {
                        message.Message = keyValuePairs.Where(w => w.Key != "{OriginalFormat}")?.ToDictionary(w => w.Key, w => w.Value);
                        message.State = null;
                    }

                    WriteLog(formatter, message);
                }
                catch (Exception ex)
                {
                    do
                    {
                        Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
                    } while ((ex = ex.InnerException) != null);
                };
            }
        }

        static string ip = "";
        private TraceOption GetTrace()
        {
            var traceOption = new TraceOption();
            var act = Activity.Current;
            if (string.IsNullOrEmpty(ip))
            {
                lock (ip)
                {
                    if (string.IsNullOrEmpty(ip))
                    {
                        ip = GetLocalIp();
                    }
                }
            }

            if (act != null)
            {
                traceOption.AppId = AppId;
                traceOption.TraceId = act.TraceId.ToString();
                traceOption.ParentSpanId = act.ParentSpanId.ToString();
                traceOption.SpanId = act.SpanId.ToString();
                traceOption.ServerIp = ip;
            }
            return traceOption;
        }
        private static string GetLocalIp()
        {
            var addressList = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList;
            var ips = addressList.Where(address => address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    .Select(address => address.ToString()).ToArray();
            if (ips.Length == 1)
            {
                return ips.First();
            }
            return ips.Where(address => !address.EndsWith(".1")).FirstOrDefault() ?? ips.FirstOrDefault();
        }
    }
}
